/***************************************************************************
 funcs.cpp  -  description
 -------------------
 begin                : Tue Aug 27 2002
 copyright            : (C) 2002 by Michiel Visser
 email                :
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "expr.h"
#include "parser.h"
//sdk stuff
#ifdef __WIN__
#include<Windows.h>
#else
#include <readline/readline.h>
#include <readline/history.h>
#include <unistd.h>
#include <fcntl.h>
#endif
#include <time.h>
#include <math.h>
#include <sys/stat.h>
#include "random.h"
#include <dirent.h>


#define CHECKTRUE(x) { if (!(x)) { _TRACE; } }
#define CHECKFALSE(x) { if ((x)) { _TRACE; } }

namespace Expression {

#define FUNC_EXPR(x) \
	IFDEBUG(void x##Expr::printTyle() const {printf(#x); } ) \
	void x##Expr::evaluateToCopy(void *space)

FUNC_EXPR(parse) {
	Object *r = session.getStackVar(0).val->value();
	EPtr res;
	TyleParser(r->toString(), res, space);
	if (!!res)
		new (space) ExprObject(res);
}

FUNC_EXPR(load) {
	Object *r = session.getStackVar(0).val->value();
	int begin = 0, length = 0;
	bool hasLength = false;
	String filename;
	if (r->getTypeNum() == TOT_List) {
		OListPtr ol(r->getConstList());
		int oll = ol->length();
		filename = (*ol)[0]->toString();
		if (oll > 1)
			begin = (*ol)[1]->toInt();
		if (oll > 2 && (*ol)[2]->value()->getTypeNum() != TOT_Null) {
			hasLength = true;
			length = (*ol)[2]->toInt();
		}
	} else
		filename = r->toString();
	FILE *f = fopen(filename, "rb");
	if (f) {
		fseek(f, 0, SEEK_END);
		int l = ftell(f);
		if (begin < 0)
			begin += l;
		if (begin > l)
			begin = l;
		fseek(f, begin, SEEK_SET);
		l -= begin;
		if (hasLength) {
			if (length < 0) {
				l += length;
				if (l < 0)
					l = 0;
			} else if (length < l)
				l = length;
		}
		String s(l);
		int al = fread((char *) s, 1, l, f);
		fclose(f);
		new (space) StringObject(s, al);
		return;
	}
	new (space) NullObject();
}

FUNC_EXPR(save) {
	Object *r = session.getStackVar(0).val->value();
	int begin = 0;
	bool truncate = true;
	String filename;
	if (r->getTypeNum() == TOT_List) {
		const OList &ol = r->getList();
		int oll = ol.length();
		filename = ol[0]->toString();
		if (oll > 1) {
			truncate = false;
			begin = ol[1]->toInt();
		}
	} else
		filename = r->toString();
	FILE *f = truncate ? 0 : fopen(filename, "r+b");
	if (!f)
		f = fopen(filename, "wb");
	else {
		if (begin < 0)
			fseek(f, begin + 1, SEEK_END);
		else
			fseek(f, begin, SEEK_SET);
	}
	if (f) {
		String s = session.getStackVar(1).val->toString();
		fwrite((char *) s, s.length(), 1, f);
		fclose(f);
	}
	new (space) NullObject();
}
FUNC_EXPR(truncate)
{	Object *r=session.getStackVar(0).val->value();
	int status=0;
	const OList &ol=r->getList();
	if (ol.length()>=2)
	{	//truncate(ol[0]->toString(),ol[1]->toInt());
#ifdef __WIN__
		HANDLE file = CreateFileA( ol[0]->toString(), GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, 0, 0 );
		if (file != INVALID_HANDLE_VALUE)
		{
			if (SetFilePointer( file, ol[1]->toInt(), NULL, FILE_BEGIN ))
			{
				SetEndOfFile (file);
			}
			CloseHandle( file );
		}
		else
			status = -1;
#else
		status=truncate(ol[0]->toString(),  ol[1]->toInt());
#endif
	}
	if (status)
		new (space) IntegerObject(status);
	else
		new (space) NullObject();
}
static int timeMode; // 0=local, 1=gmt

static void getTime(const time_t t, void *space) {
	OList l;
	int *bt = (int *) (timeMode ? gmtime(&t) : localtime(&t));
	bt[4] += 1;
	bt[5] += 1900;
	for (int i = 0; i < 6; i++) {
		l.prepend(NEW_OBJ IntegerObject(bt[i]));
	}
	new (space) ListObject(l);
}

FUNC_EXPR(info) {
	Object * r = session.getStackVar(0).val;
	struct stat buffer;
	int begin = 0, length = 0, base = 0;
	if (stat((char *) r->toString(), &buffer) == 0) //length,permission,date
			{
		OList l;
		l.prepend(NEW_OBJ IntegerObject(buffer.st_mode));
		l.prepend(NEW_OBJ IntegerObject(buffer.st_size));
		l.prepend(ao());
		getTime(buffer.st_mtime, *l);
		new (space) ListObject(l);
		return;
	}
	new (space) NullObject();
}
FUNC_EXPR(getDir)
{	Object * r=session.getStackVar(0).val;
	OList l;
#ifdef __WIN__
	String s(r->toString(),"\\*");
	_WIN32_FIND_DATAA data;
	HANDLE h=FindFirstFileA((char *)s,&data);
	if (h!=INVALID_HANDLE_VALUE)
	{
		do
		{	if (data.cFileName[0]!='.'||((data.cFileName[1]!='.'||data.cFileName[2]!=0)&&data.cFileName[1]!=0))
			l.prepend(NEW_OBJ StringObject(data.cFileName));
		} while(FindNextFileA(h,&data));
		FindClose(h);
	}
#else
	String s(r->toString());
	DIR           *d;
	struct dirent *dir;
	d = opendir((char *)s);
	if (d) {
		while ((dir = readdir(d)) != NULL) {
			l.prepend(NEW_OBJ StringObject(dir->d_name));
		}
		closedir(d);
	}
	else {
		new (space) NullObject();
		return;
	}
#endif
	new (space) ListObject(l);
}

FUNC_EXPR(sleep) {
	int i = session.getStackVar(0).val->toInt();
	if (i < 0)
		i = 0;
	session.leave();
#ifdef __WIN__
	Sleep(i);
#else
	usleep(i * 1000);
#endif
	session.enter();
#ifdef MALLOC_COUNT
	new (space) IntegerObject(mcounts.variable+mcounts.block);
	return;
#else
	new (space) NullObject();
#endif
}

FUNC_EXPR(random) {
	IFDEBUG( dbgvars[session.getStackVar(0).val->toInt()]=session.getStackVar(1).val->toInt();)
	new (space) IntegerObject(RNG());
}
#ifdef __WIN__
FUNC_EXPR(setCb)
{
	String s (session.getStackVar(0).val->toString() );
	HANDLE nd = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, strlen(s)+2);
	LPSTR ns = (LPSTR)GlobalLock(nd);
	strcpy(ns,s);
	GlobalUnlock(nd);
	OpenClipboard(NULL);
	EmptyClipboard();
	SetClipboardData(/*CF_OEMTEXT*/CF_TEXT, nd);
	CloseClipboard();
	new (space) NullObject();
}

FUNC_EXPR(getCb)
{
	HANDLE nd;
	LPSTR ns;
	OpenClipboard(NULL);
	nd=GetClipboardData(CF_TEXT);
	if (nd) {
		ns = (LPSTR)GlobalLock(nd);
		String s((char *)ns);
		GlobalUnlock(nd);
		new (space) StringObject(s);
	}
	else {
		new (space) StringObject("");
	}
	CloseClipboard();
}
#endif
FUNC_EXPR(sys)
{
	Object * r=session.getStackVar(0).val;
	Object::HolderCP lo;
	new (lo) ListObject();
	lo.protectChilds();
	OList::P pl(&*(ListObject&)*lo);
#ifdef __WIN__
	SECURITY_ATTRIBUTES saAttr;
	saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
	saAttr.bInheritHandle = TRUE;
	saAttr.lpSecurityDescriptor = NULL;

	HANDLE hChildStdoutRd, hChildStdoutWr, hChildStdinRd,hChildStdinWr,
	hChildStdoutRdDup, hChildStdinWrDup;
	CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0);
	DuplicateHandle(GetCurrentProcess(), hChildStdoutRd,GetCurrentProcess(), &hChildStdoutRdDup, 0, FALSE, DUPLICATE_SAME_ACCESS);
	CloseHandle(hChildStdoutRd);
	CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0);
	DuplicateHandle(GetCurrentProcess(), hChildStdinWr, GetCurrentProcess(), &hChildStdinWrDup, 0, FALSE, DUPLICATE_SAME_ACCESS);
	CloseHandle(hChildStdinWr);

	STARTUPINFO si;
	memset(&si,0,sizeof(si));
	si.cb = sizeof(si);
	si.hStdError = hChildStdoutWr;
	si.hStdOutput = hChildStdoutWr;
	si.hStdInput = hChildStdinRd;
	si.dwFlags |= STARTF_USESTDHANDLES;

	PROCESS_INFORMATION pi;
	String command;
	if (r->getTypeNum()==TOT_List)
	{	const OList &ol=r->getList();
		if (ol.length()>1)
		{	command=ol[0]->toString();
			DWORD dwWritten;
			String in( ol[1]->toString() );
			WriteFile(hChildStdinWrDup, (char *)in, in.length()+1, &dwWritten, NULL);
		}
		else
		command=r->toString();
	}
	else
	command=r->toString();

	if(!CreateProcess(0, (char *)command,0,0,TRUE,CREATE_NO_WINDOW,0,0,/*in*/&si,/*out*/&pi) )
	{	CloseHandle(hChildStdoutWr);
		CloseHandle(hChildStdinRd);
		CloseHandle(hChildStdinWrDup);
		CloseHandle(hChildStdoutRdDup);
		{	new (space) NullObject(); return;}
	}
	session.leave();
	for(;;) {
		DWORD result=WaitForSingleObject(pi.hProcess, 1);
		DWORD numberOfBytes;
		PeekNamedPipe(hChildStdoutRdDup,0,0,0,&numberOfBytes,0);
		if (numberOfBytes) {
			session.enter();
			{
				String buf(numberOfBytes);
				DWORD dwRead;
				ReadFile( hChildStdoutRdDup, (char *)buf, numberOfBytes, &dwRead, NULL );
				pl->prepend(NEW_OBJ StringObject(buf));
				pl++;
			}
			session.leave();
		}
		if (result!=WAIT_TIMEOUT) break;
	}
	DWORD status;
	GetExitCodeProcess(pi.hProcess, &status);
	CloseHandle(hChildStdoutWr);
	CloseHandle(hChildStdinRd);
	CloseHandle(hChildStdinWrDup);
	CloseHandle(hChildStdoutRdDup);
	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);

	session.enter();
#else
	int status=0;
	String command, in;
	if (r->getTypeNum()==TOT_List)
	{	const OList &ol=r->getList();
		if (ol.length()>1)
		{	command=ol[0]->toString();
			in = ol[1]->toString();
		}
		else
			command=r->toString();
	}
	else
		command=r->toString();
	FILE * file = popen(command, !!in?"w":"r");
	if (file) {
		session.leave();
		if (!!in) {
			fwrite((const char *)in, 1, in.strlen()+1, file);
		}
		else {
			{
				int fd = fileno(file);
				int flags;
				flags = fcntl(fd, F_GETFL, 0);
				flags |= O_NONBLOCK;
				fcntl(fd, F_SETFL, flags);
			}
			char buf[10000];
			usleep(10000);
			while(fgets(buf, sizeof(buf), file) != NULL) {
				session.enter();
				pl->prepend(NEW_OBJ StringObject(buf));
				pl++;
				session.leave();
			}
		}
		pclose(file);
		session.enter();
	}
	else
		status=-1;
#endif
	OList rlist;
	rlist.prepend(NEW_OBJ StringObject(lo->toString()));
	rlist.prepend(NEW_OBJ IntegerObject(status));
	new (space) ListObject(rlist);
}
FUNC_EXPR(puts) {
	printf("%s", (char *) (session.getStackVar(0).val->toString()));
	new (space) NullObject();
}

FUNC_EXPR(gets) {
	Object *r=session.getStackVar(0).val;
	if (r->getTypeNum()!=TOT_Null) {
#ifdef __WIN__
		printf("%s", (char *) (r->toString()));
#else
		char *buf=readline(r->toString());
		if (buf) {
			add_history(buf);
			new (space) StringObject(buf);
		} else
			new (space) NullObject();
		return;
#endif
	}
	String str(16384);
	session.leave();
	char *res=fgets((char *) str, str.length(), stdin);
	session.enter();
	if (res)
		new (space) StringObject((char *) str);
	else
		new (space) NullObject();
}

FUNC_EXPR(toAscii) {
	new (space) IntegerObject(
			(unsigned char) ((session.getStackVar(0).val->toString())[0]));
}

FUNC_EXPR(fromAscii) {
	char s[1];
	s[0] = (char) (session.getStackVar(0).val->toInt());
	new (space) StringObject(s, 1);
}
#ifdef __WIN__
FUNC_EXPR(winGetFirst)
{
	Object * p=session.getStackVar(0).val->value();
	new (space) IntegerObject((int)GetTopWindow(p->vtable == &vtable_Null ? NULL: (HWND)(p->toInt() )) );
}

FUNC_EXPR(winGetNext)
{
	new (space) IntegerObject((int)GetNextWindow((HWND)(session.getStackVar(0).val->toInt()), GW_HWNDNEXT ) );
}

FUNC_EXPR(winGetParent)
{
	new (space) IntegerObject((int)GetParent((HWND)(session.getStackVar(0).val->toInt()) ) );
}

FUNC_EXPR(winSendKey)
{
	OListPtr l( session.getStackVar(0).val->getConstList() );
	int ll=l->length();
	if (ll)
	keybd_event( (*l)[0]->toInt(), ll<=2?0:(*l)[2]->toInt(), ll<=1?0:(*l)[1]->toInt(), 0);
	//PostMessage( (HWND)(l[0]->toInt()), l[1]->toInt(), ll<=2?0:l[2]->toInt(), ll<=3?0:l[3]->toInt() );
	new (space) NullObject();
}

FUNC_EXPR(winGetKeyState)
{
	new (space) IntegerObject( GetAsyncKeyState ( session.getStackVar(0).val->toInt() ) );
}

FUNC_EXPR(winGetIdleTime)
{
	LASTINPUTINFO info;
	info.cbSize = sizeof(LASTINPUTINFO);
	if (GetLastInputInfo(&info) )
	new (space) IntegerObject( GetTickCount() - info.dwTime );
	else
	new (space) NullObject();
}

FUNC_EXPR(winSendMouseEvent) // motion=0x8000, absolute=0x10000000, normalised=0x20000000
{
	OListPtr l ( session.getStackVar(0).val->getConstList() );
	int ll=l->length();
	POINT p = {0,0};
	int f=(*l)[0]->toInt() ^ 0x8000; // default must be absolute instead of relative.
	if (ll>2) {
		f |= 1;
		int x=(*l)[1]->toInt();
		int y=(*l)[2]->toInt();
		if ( (f&0x10000000)==0 ) // relative
		{	ClientToScreen(GetForegroundWindow(),&p);
		}
		if (x<0||y<0)
		{	RECT r;
			GetClientRect((f&0x10000000) ? GetDesktopWindow() : GetForegroundWindow(),&r);
			if (x<0)
			p.x+=r.right-r.left;
			if (y<0)
			p.y+=r.bottom-r.top;
		}
		if ( (f&0x20000000)==0 ) {p.x += x; p.y += y;} // not normalised coords, so add coords before normalisation
		RECT r;
		GetClientRect(GetDesktopWindow(),&r);
		p.x = (p.x*0x10000)/(r.right-r.left);
		p.y = (p.y*0x10000)/(r.bottom-r.top);
		if ( (f&0x20000000)!=0 ) {p.x += x; p.y += y;} // normalised, so add after normalisation
	}
	mouse_event( f, p.x, p.y, ll<=3?0:(*l)[3]->toInt(), 0 );
	new (space) NullObject();
}

FUNC_EXPR(winGetMousePos)
{
	POINT p;
	GetCursorPos(&p);
	if ( session.getStackVar(0).val->toInt() == 0 ) ScreenToClient(GetForegroundWindow(), &p);
	OList l;
	l.prepend(NEW_OBJ IntegerObject(p.y));
	l.prepend(NEW_OBJ IntegerObject(p.x));
	new (space) ListObject(l);
}

FUNC_EXPR(winActivate)
{
	HWND w = (HWND) (session.getStackVar(0).val->toInt());
	WINDOWPLACEMENT wp;
	wp.length=sizeof(wp);
	GetWindowPlacement(w,&wp);
	if (wp.showCmd==SW_SHOWMINIMIZED)
	ShowWindow(w, SW_SHOWNORMAL );
	SetWindowPos(w,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);
	SetForegroundWindow(w);
	SetForegroundWindow(w); // must be done twice
	SetWindowPos(w,HWND_NOTOPMOST,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);
	new (space) NullObject();
}
FUNC_EXPR(winGetPixel)
{
	OListPtr l( session.getStackVar(0).val->getConstList() );
	int len=l->length();
	if (len<2) {new (space) NullObject(); return;}
	HWND win = (len>2 && (*l)[2]->vtable != &vtable_Null) ? (HWND)(*l)[2]->toInt() : GetForegroundWindow();
	HDC hdc=GetDC(win);
	int c=(int)GetPixel(hdc,(*l)[0]->toInt(),(*l)[1]->toInt());
	ReleaseDC(win,hdc);
	new (space) IntegerObject(c);
}

FUNC_EXPR(winGetActive)
{
	new (space) IntegerObject( (int)(GetForegroundWindow()) );
}

FUNC_EXPR(winGetName)
{
	char str[1024];
	GetWindowText((HWND) (session.getStackVar(0).val->toInt() ), str,sizeof(str));
	new (space) StringObject(str);
}
#endif
FUNC_EXPR(getDateTime) {
	time_t t = time(0);
	getTime(t, space);
}

FUNC_EXPR(setTimeMode) {
	timeMode = session.getStackVar(0).val->toInt();
	new (space) NullObject();
}
FUNC_EXPR(getTickCount)
{
#ifdef __WIN__
	new (space) IntegerObject((int)GetTickCount());
#else
	struct timespec t;
	clock_gettime(CLOCK_MONOTONIC, &t);
	new (space) IntegerObject((int)((t.tv_nsec/1000000)+t.tv_sec*1000));
#endif
}
FUNC_EXPR(hash) {
	new (space) IntegerObject(session.getStackVar(0).val->hash());
}

FUNC_EXPR(getGlobals) {
	session.getGlobalNamesObject()->duplicateIn(space);
}

FUNC_EXPR(getTypes) {
	session.m_typeNames.m_names->duplicateIn(space);
}
#ifdef __WIN__
void newThread(LPVOID p)
#else
void *newThread(void *p)
#endif
{
	Thread *t=(Thread *)p;
#ifndef __WIN__
	t->setToCurrent();
#endif
	session.enter();
	session.pushStack(NEW_OBJ IntegerObject((int)t->m_father));
	t->m_func->evaluateToVoid();
	session.popStack();
	session.m_curThread = 0;
	delete t;
	session.leave();
#ifdef __WIN__
	ExitThread(0);
#else
	return 0;
#endif
}

FUNC_EXPR(spawn) //evalstr,priority
{
	unsigned long threadId;
	int priority=0;
	Object * func=session.getStackVar(0).val->value();
	if (func->getTypeNum()==TOT_List) {
		OListPtr l ( func->getConstList() );
		int ll=l->length();
		if (ll>0) func=(*l)[0];
		if (ll>1) priority=(*l)[1]->toInt();
	}
	if (func->getTypeNum() == TOT_Expr)
	{
		Thread *t = new Thread( session.m_curThread, func->copy() );
#ifdef __WIN__
		//SHOW(x,GetThreadPriority(GetCurrentThread())); todo, set main thread prio
		HANDLE h=CreateThread(NULL,0x20000,(LPTHREAD_START_ROUTINE)newThread,t,CREATE_SUSPENDED,&threadId);
		SetThreadPriority(h,priority);
		t->setVars(h, threadId);
#else
		pthread_t pt;
		pthread_create(&pt, NULL, newThread, t);
#endif
		new (space) IntegerObject( (int)t );
#ifdef __WIN__
		ResumeThread(h);
#else
#endif
		session.schedule();
		return;
	}
	new (space) NullObject();
}

FUNC_EXPR(threadPut) {
	Thread *t = (Thread *) session.getStackVar(0).val->toInt();
	if (session.m_threads.contains(t)) {
		Object * o = session.getStackVar(1).val;
		o->duplicateIn(t->m_incoming.end().prependalloc(OBJ_SIZE));
		t->m_sem.release();
	}
	session.schedule();
	new (space) NullObject();
}

FUNC_EXPR(threadGet) {
	Thread *t = session.m_curThread;
	if (!t->m_incoming) {
		int timeout;
		{
			Object * o = session.getStackVar(0).val->value();
			timeout = o->vtable == &vtable_Null ? -1 : o->toInt();
		}
		session.leave();
		if (timeout < 0)
			t->m_sem.claim();
		else
			t->m_sem.claim(timeout);
		session.enter();
		if (!t->m_incoming) {
			new (space) NullObject();
			return;
		}
	} else
		session.m_curThread->m_sem.claim(0); //try to claim to get event when list is empty
	t->m_incoming->duplicateIn(space);
	t->m_incoming.removeone();
}

FUNC_EXPR(operateMath) {
	float r = session.getStackVar(1).val->toFloat();
	switch (session.getStackVar(0).val->toInt()) {
	case 0:
		r = sqrt(r);
		break;
	case 1:
		r = exp(r);
		break;
	case 2:
		r = log(r);
		break;
	case 3:
		r = sin(r);
		break;
	case 4:
		r = cos(r);
		break;
	case 5:
		r = tan(r);
		break;
	case 6:
		r = asin(r);
		break;
	case 7:
		r = acos(r);
		break;
	case 8:
		r = atan(r);
		break;
	case 9:
		r = floor(r);
		break;
	case 10:
		r = ceil(r);
		break;
	case 11:
		r = tanh(r);
		break;
	}
	new (space) FloatObject(r);
}

#ifdef DEBUGLOGGING
FUNC_EXPR(getVarLog) {
	session.getLog(space);
}
FUNC_EXPR(setVarLogSize) {
	session.setLogSize(session.getStackVar(0).val->toInt());
	new (space) NullObject();
}
#endif

} // namespace
